<?php
namespace Library\Dal;

abstract class Model
{
	protected $primaryKey;
	protected $connection;
	protected $table;
	
	protected $_adapter;
	protected $_reflection;
	protected $_config;
	protected $_methods = array();
	protected $_modelMemory = array();
	
	private static $_dbSettings;
	
	public function __construct($data = array(), $forceLoading = true)
	{		
		$this->_reflection = new \ReflectionClass($this);
		$this->_adapter = new \Library\Dal\Driver\Mysql\MysqlAdapter($this->connection);
		$this->_config = \Library\Platform::getConfig();
		
		if (is_array($data) && count($data) == 0)
		{
			return;
		}
		
		if (!is_array($data))
		{
			$this->_reflection->getProperty($this->primaryKey)->setValue($this, $data);
		}
		else
		{
			foreach($data as $key => $value)
			{
				$this->_reflection->getProperty($key)->setValue($this, $value);
			}
		}
		
		if ($forceLoading)
		{
			$this->load();
		}
		
		$this->_readRelations();
	}
    
    public static function all($fields = array(), $config = array())
    {
        $reflection = new \ReflectionClass(get_called_class());
        $class = $reflection->getName();
        $instance = new $class;
		$activeRecord = new \Library\Dal\Driver\Mysql\ActiveRecord();
		$arWhere = array();
        
        $ignoreMethod = $reflection->getMethod('_ignoreField');
        $ignoreMethod->setAccessible(true);
		
		foreach($fields as $key => $value)
		{
			if ($ignoreMethod->invoke($instance, $key))
			{
				continue;
			}
			
			if ($value !== null)
			{
				$arWhere[$key . " = ?"] = $value;
			}
		}
        
        $tableProp = $reflection->getProperty('table');
        $tableProp->setAccessible(true);
        
        $adapterProp = $reflection->getProperty('_adapter');
        $adapterProp->setAccessible(true);

		$query = $activeRecord->select('*')
							  ->from($tableProp->getValue($instance))
							  ->where($arWhere);
        
        foreach($config as $key => $value)
        {
            if (is_array($value))
            {
                call_user_func_array(array($activeRecord, $key), $value);
            }
            else
            {
                $query->$key($value);
            }
        }
		
		$result = $adapterProp->getValue($instance)->fetchAll($query);
        $list = array();
        
        foreach($result as $row)
        {
            $list[] = new $class($row);
        }
        
        return $list;
    }
	
	private function _ignoreField($field)
	{
		$settings = $this->_getDbSettings();
		$model_name = $this->_reflection->getShortName();
		
		if (isset($settings[$model_name][$field]['ignore']) && $settings[$model_name][$field]['ignore'])
		{
			return true;
		}
		
		return false;
	}
	
	public function getData()
	{
		$fields = $this->_reflection->getProperties(\ReflectionProperty::IS_PUBLIC);
		$data = array();
		
		foreach($fields as $prop)
		{
			if ($prop->getDeclaringClass()->getName() != get_class($this))
			{
				continue;
			}
			
			$data[$prop->getName()] = $prop->getValue($this);
		}
		
		return $data;
	}
	
	public function __call($method, $args)
	{
		if (isset($this->_methods[$method]))
		{
			$info = $this->_methods[$method];
			
			return $this->_getReferenceModel($info);
		}
		
		if (substr($method, 0, 4) == 'set_' || substr($method, 0, 4) == 'get_' )
		{
			$field = substr($method, 4);
			$settings = $this->_getDbSettings();
			$model_name = $this->_reflection->getShortName();
			$value = isset($args['0']) ? $args['0'] : null;
			
			if (isset($settings[$model_name]) && isset($settings[$model_name][$field]))
			{
				//Encrypt the value
				if (isset($settings[$model_name][$field]['encryption']))
				{
					$value = hash($settings[$model_name][$field]['encryption'], (isset($settings[$model_name][$field]['salt']) ? $settings[$model_name][$field]['salt'] : null) . $value);
				}
			}
				
			switch(substr($method, 0, 3))
			{
				case "get":
					return $value;
					break;
				case "set":
					$this->_reflection->getProperty($field)->setValue($this, $value);
			}
			
			return;
		}
		
		throw new \Exception('Method ' . $method . ' no reference or usable method found.');
	}
	
	private function _getReferenceModel($keyInfo)
	{
		if (!isset($this->_modelMemory[$keyInfo['id']]))
		{
			$class = '\\' . $this->_config['application']['module'] . '\\Models\\' . $keyInfo['referenceClass'];
			$model = new $class;

			$model->{$keyInfo['referenceKey']} = $this->{$keyInfo['key']};
			$model->load();
			
			$this->_modelMemory[$keyInfo['id']] = $model;
		}
		
		return $this->_modelMemory[$keyInfo['id']];
	}
	
	private function _readRelations()
	{
		$settings = $this->_readDbSettings();
		$model_name = $this->_reflection->getShortName();
		
		if (!isset($settings[$model_name]))
		{
			return;
		}
		
		$relations = $settings[$model_name];
		
		foreach($relations as $key => $info)
		{
			if (!isset($info['reference']))
			{
				break;
			}

			$parts = explode('.', $info['reference']);
			
			if ($parts['1'] == 'primary')
			{
				$class = '\\' . $this->_config['application']['module'] . '\\Models\\' . $parts['0'];
				$object = new $class;
				$reflection = new \ReflectionClass($object);
				
				$property = $reflection->getProperty('primaryKey');
				$property->setAccessible(true);
				
				$info['referenceKey'] = $property->getValue($object);
			}
			else
			{
				$info['referenceKey'] = $settings[$parts['0']][$parts['1']]['key'];
			}

			$info['referenceClass'] = $parts['0'];
			$info['id'] = $key;
			
			$this->_methods[$info['method']] = $info;
		}
	}
	
	private function _readDbSettings()
	{
		$settings = array();
		
		if (file_exists($this->_config['application']['path'] . '/config/database.ini'))
		{
			$ini = new \Library\Config\Ini($this->_config['application']['path'] . '/config/database.ini');
			$settings = $ini->getData();
		}
		
		return $settings;
	}
	
	private function _getDbSettings()
	{
		if (self::$_dbSettings === null)
		{
			self::$_dbSettings = $this->_readDbSettings();
		}

		return self::$_dbSettings;
	}
	
	public function load()
	{
		$fields = $this->getData();
		$activeRecord = new \Library\Dal\Driver\Mysql\ActiveRecord();
		$arFields = array();
		$arWhere = array();
		
		foreach($fields as $key => $value)
		{
			if ($this->_ignoreField($key))
			{
				continue;
			}
			
			if ($value !== null)
			{
				$arWhere[$key . " = ?"] = $value;
			}
			
			$arFields[] = $key;
		}

		$query = $activeRecord->select($arFields)
							  ->from($this->table)
							  ->where($arWhere);
		
		$result = $this->_adapter->fetchRow($query);

		if (is_array($result))
		{
			foreach($result as $key => $value)
			{
				if ($this->_reflection->hasProperty($key))
				{
					$this->_reflection->getProperty($key)->setValue($this, $value);
				}
			}
			
			return true;
		}
		
		return false;
	}
	
	public function create()
	{
		$fields = $this->getData();
		$activeRecord = new \Library\Dal\Driver\Mysql\ActiveRecord();
		$arFields = array();
		$arUpdate = array();
		
		foreach($fields as $key => $value)
		{
			if ($this->_ignoreField($key))
			{
				continue;
			}
			
			if ($value !== null)
			{
				$arUpdate[$key] = $value;
			}
			
			$arFields[] = $key;
		}
		
		$query = $activeRecord->insert($this->table, $arUpdate)
							  ->from($this->table);
		
		$this->_adapter->query($query);
		
		$this->_reflection->getProperty($this->primaryKey)->setValue($this, $this->_adapter->getLastId());
	}
	
	public function save()
	{
		if ($this->id == null)
		{
			return $this->create();
		}
		
		$fields = $this->getData();
		$activeRecord = new \Library\Dal\Driver\Mysql\ActiveRecord();
		$arFields = array();
		$arUpdate = array();
		
		foreach($fields as $key => $value)
		{
			if ($this->_ignoreField($key))
			{
				continue;
			}
			
			if ($value !== null)
			{
				$arUpdate[$key] = $value;
			}
			
			$arFields[] = $key;
		}
		
		$query = $activeRecord->update($this->table, $arUpdate)
							  ->from($this->table);
		
		$activeRecord->where(array($this->primaryKey . ' = ?' => $this->_reflection->getProperty($this->primaryKey)->getValue($this)));
		
		$this->_adapter->query($query);
	}
	
	public function delete()
	{
		
	}
}